2.9.1. 常量,switch和typedef

常量、switch 语句、枚举类型和 typedef 是 C 语言的功能,可用于创建更具可读性和可维护性的代码。常量、枚举类型和 typedef 用于定义程序中文字值和类型的别名。 switch 语句可以用来代替一些链接if-else if语句。

C Constants(常量)

常量是 C 字面值的别名。使用常量代替文字值以使代码更具可读性且更易于修改。在 C 中,常量是使用以下语法在函数体外部定义的:

#define const_name (literal_value)

以下是定义和使用三个常量(NPINAME)的部分程序示例:

#include <stdio.h>
#include <stdlib.h>

#define N    (20)        // N:  alias for the literal value 20
#define PI   (3.14)      // PI: alias for the literal value 3.14
#define NAME ("Sarita")  // NAME: alias for the string literal "Sarita"

int main(void) {
  int array[N];   // an array of 20 ints
  int *d_arr, i;
  double area, circ, radius;

  radius = 12.3;
  area = PI*radius*radius;
  circ = 2*PI*radius;

  d_arr = malloc(sizeof(int)*N);
  if(d_arr == NULL) {
    printf("Sorry, %s, malloc failed!\n", NAME);
    exit(1);
  }
  for(i=0; i < N; i++) {
    array[i] = i;
    d_arr[i] = i*2;
  }
  ...

使用常量使代码更具可读性(在表达式中,PI比 更有意义3.14)。使用常量还可以使代码更容易修改。例如,要改变上面程序中数组的界限和pi值的精度,程序员只需要改变它们的常量定义并重新编译即可;所有使用该常量的代码都将使用它们的新值。例如:

#define N    (50)        // redefine N from 20 to 50
#define PI   (3.14159)   // redefine PI to higher precision

int main(void) {
  int array[N];  // now allocates an array of size 50
  ...
  area = PI*radius*radius;        // now uses 3.14159 for PI
  d_arr = malloc(sizeof(int)*N);  // now mallocs array of 50 ints
  ...
  for(i=0; i < N; i++) {    // now iterates over 50 elements
  ...

重要的是要记住,常量不是左值——它们是 C 类型字面量(文字值)的别名。因此,它们的值不能像变量那样在运行时更改。例如,以下代码会导致编译错误:

#define N  20

int main(void) {
  ...
  N = 50;  // compilation error: `20 = 50` is not valid C

Switch 语句

C switch语句可用于代替部分(但不是全部)链接 if代码和 else if 序列。虽然switch不为 C 编程语言提供任何额外的表达能力,但它通常会产生更简洁的代码分支序列。它还可以允许编译器生成比等效链接代码更有效执行的分支ifelse if 代码。

语句的 C 语法switch如下所示:

switch (<expression>) {

   case <literal value 1>:
        <statements>;
        break;         // breaks out of switch statement body
   case <literal value 2>:
        <statements>;
        break;         // breaks out of switch statement body
   ...
   default:            // default label is optional
        <statements>;
}

switch语句的执行过程如下:

  1. 首先对expression求值。
  2. 接下来,switch搜索与case表达式的值匹配的字面量值(文字值)。
  3. 找到匹配的case文字后,它开始执行紧随其后的语句。
  4. 如果没有case找到匹配,它将开始执行标签中的语句 default(如果存在)。
  5. 否则,语句主体中的任何语句switch都不会被执行。

关于switch语句的一些规则:

  • 与每个关联的值case必须是字面量值(文字值) - 它 不能 是表达式。原始表达式 仅与与每个关联的文字值进行 相等 case匹配。
  • 到达break语句将停止该语句体内所有剩余语句的执行switch。也就是说,break跳出语句主体switch并继续执行整个块之后的下一条语句switch
  • 具有匹配值的语句标记case将要执行的 C 语句序列的起点 - 执行跳转到主体内的某个位置switch以开始执行代码。因此,如果特定 case 的末尾没有 break 语句,则后续语句下的case语句将按顺序执行,直到执行一条 break 语句或到达 switch 语句体的末尾。
  • 标签default是可选的。如果存在,则必须位于末尾。

这是一个带有语句的示例程序switch

#include <stdio.h>

int main(void) {
    int num, new_num = 0;

    printf("enter a number between 6 and 9: ");
    scanf("%d", &num);

    switch(num) {
        case 6:
            new_num = num + 1;
            break;
        case 7:
            new_num = num;
            break;
        case 8:
            new_num = num - 1;
            break;
        case 9:
            new_num = num + 2;
            break;
        default:
            printf("Hey, %d is not between 6 and 9\n", num);
    }
    printf("num %d  new_num %d\n", num, new_num);
    return 0;
}

以下是此代码的一些运行示例:

./a.out
enter a number between 6 and 9: 9
num 9  new_num 11

./a.out
enter a number between 6 and 9: 6
num 6  new_num 7

./a.out
enter a number between 6 and 9: 12
Hey, 12 is not between 6 and 9
num 12  new_num 0

枚举类型

枚举类型(enum)是一种定义一组相关整型常量的方法。 switch 语句和枚举类型经常一起使用。

枚举类型应在函数体外部定义,使用以下语法(enum是 C 中的关键字):

enum type_name {
   CONST_1_NAME,
   CONST_2_NAME,
   ...
   CONST_N_NAME
};

请注意,常量字段由逗号分隔的名称列表指定,并且不是显式给出的值。默认情况下,列表中的第一个常量被分配值 0,第二个常量被分配值 1,依此类推。

下面是为一周中的几天定义枚举类型的示例:

enum days_of_week {
   MON,
   TUES,
   WED,
   THURS,
   FRI
};

枚举类型值的变量使用类型名称来声明 enum type_name,并且它定义的常量值可以在表达式中使用。例如:

enum days_of_week day;

day = THURS;

if (day > WED) {
  printf("The weekend is arriving soon!\n");
}

枚举类型类似于定义一组常量,#define 如下所示:

#define MON    0
#define TUES   1
#define WED    2
#define THURS  3
#define FRI    4

枚举类型中的常量值的使用方式与常量的使用方式类似,可以使程序更易于阅读,代码更易于更新。然而,枚举类型的优点是可以将一组相关的整数常量组合在一起。它也是一个类型定义,因此变量和参数可以声明为枚举类型,而常量是文字值的别名。此外,在枚举类型中,每个常量的具体值都是从 开始按顺序隐式分配的0,因此程序员不必指定每个常量的值。

枚举类型的另一个很好的功能是可以轻松地在集合中添加或删除常量,而无需更改它们的所有值。例如,如果用户想要将星期六和星期日添加到日期集中并维护日期的相对顺序,他们可以将它们添加到枚举类型定义中,而不必像需要时那样显式地重新定义其他值使用#define常量定义:

enum days_of_week {
   SUN,        // SUN will now be 0
   MON,        // MON will now be 1, and so on
   TUES,
   WED,
   THURS,
   FRI,
   SAT
};

尽管值被隐式分配给枚举类型的常量,但程序员也可以使用= val语法为它们分配特定的值。例如,如果程序员希望星期几的值从 1 而不是 0 开始,他们可以执行以下操作:

enum days_of_week {
   SUN = 1,  // start the sequence at 1
   MON,      // this is 2 (next value after 1)
   TUES,     // this is 3, and so on
   WED,
   THURS,
   FRI,
   SAT
};

由于枚举类型为一组int文字值定义了别名,因此枚举类型的值将作为其int值而不是别名的名称打印出来。例如,给定上述 enum days_of_week 的定义,以下打印内容3不打印字符串"TUES"

enum days_of_week day;

day = TUES;
printf("Today is %d\n", day);

枚举类型通常与 switch 语句结合使用,如下面的示例代码所示。该示例还显示了一个 switch 语句,其中多个 case 语句关联同一组语句, 某个 case 语句在下一个 case 语句前没有 break 语句(当 valFRI, 两个 printf 语句会在遇到break语句之前执行, 因为在 break 之前 MONWED 中的只有一个 printf语句):

// an int because we are using scanf to assign its value
int val;

printf("enter a value between %d and %d: ", SUN, SAT);
scanf("%d", &val);

switch (val) {
  case FRI:
     printf("Orchestra practice today\n");
  case MON:
  case WED:
     printf("PSYCH 101 and CS 231 today\n");
     break;
  case TUES:
  case THURS:
     printf("Math 311 and HIST 140 today\n");
     break;
  case SAT:
     printf("Day off!\n");
     break;
  case SUN:
     printf("Do weekly pre-readings\n");
     break;
  default:
     printf("Error: %d is not a valid day\n", val);
};

类型定义( typedef)

C 提供了一种使用关键字定义新类型的方法,该新类型是现有类型的别名typedef。定义后,可以使用该类型的新别名来声明变量。此功能通常用于使程序更具可读性并使用较短的类型名称(通常用于结构和枚举类型)。以下是定义新类型的格式typedef

typedef existing_type_name new_type_alias_name;

下面是一个使用 typedef 的部分程序示例:

#define MAXNAME  (30)
#define MAXCLASS (40)

enum class_year {
  FIRST = 1,
  SECOND,
  JUNIOR,
  SENIOR,
  POSTGRAD
};

// classYr is an alias for enum class_year
typedef enum class_year classYr;

struct studentT {
  char name[MAXNAME];
  classYr year;     // use classYr type alias for field type
  float gpa;
};

// studentT is an alias for struct studentT
typedef struct studentT  studentT;

// ull is an alias for unsigned long long
typedef unsigned long long ull;

int main(void) {

  // declare variables using typedef type names
  studentT class[MAXCLASS];
  classYr yr;
  ull num;

  num = 123456789;
  yr = JUNIOR;
  strcpy(class[0].name, "Sarita");
  class[0].year = SENIOR;
  class[0].gpa = 3.75;

  ...

由于 typedef 通常与结构一起使用,因此 C 提供了将 typedef 和结构定义组合在一起的语法,方法是在结构定义中添加前缀 typedef 并在结构定义的结束后 } 列出类型别名的名称。例如,以下定义了struct studentT名为 的类型的结构类型和别名studentT

typedef struct studentT {
  char name[MAXNAME];
  classYr year;     // use classYr type alias for field type
  float gpa;
} studentT;

此定义相当于在结构体定义之后单独进行 typedef,如上例所示。